/*************************************************************************
 *                                                                       *
 * This file is part of the FreeMAPs distribution.  For full copyright   *
 * information, see COPYRIGHT and COPYING.LESSER.                        *
 *                                                                       *
 * Author:           sqbang                                              *
 * Email:            sqbang@ustc.edu                                     *
 * Date:             2011.08.22                                          *
 *                                                                       *
 *************************************************************************/

//declaration header
#include "COMMON/smlt/SimulationEnsemble.h"

//COMMON headers
#include "COMMON/equa/PDEEquationEnsembleStrategy.h"
#include "COMMON/info/InfoManager.h"
#include "COMMON/info/MeshInfo.h"
#include "COMMON/tbox/LogMessage.h"
#include "COMMON/tbox/MathUtilities.h"
#include "COMMON/tbox/Utilities.h"



namespace FreeMAPs
{

SimulationEnsemble*
SimulationEnsemble::s_instance (NULL);



StartupShutdownManager::Handler
SimulationEnsemble::s_shutdown_handler(
    0,
    0,
    SimulationEnsemble::shutdown_callback,
    0,
    StartupShutdownManager::priorityRestartManager);



SimulationEnsemble*
SimulationEnsemble::get_instance()
{
    if (!s_instance)
    {
        s_instance = new SimulationEnsemble ("simulation_ensemble");
    }
    return s_instance;
}



void
SimulationEnsemble::shutdown_callback()
{
    if (s_instance)
    {
        delete s_instance;
        s_instance = ((SimulationEnsemble*)NULL);
    }
}



SimulationEnsemble::SimulationEnsemble (
    const std::string& object_name):
    Parent ("ROOT", object_name)
{}



SimulationEnsemble::~SimulationEnsemble()
{
    this->clear();
}



void
SimulationEnsemble::clear()
{
    Utilities::clear_pointer_object (d_pde_equation_ensembles);
//    Utilities::clear_pointer_object (d_nonpde_equation_ensembles);
}



bool
SimulationEnsemble::check_all_info_db (
        const InputDatabase* db)
{
    //equation_ensembles
    if( !db->key_exists("equation_ensembles") )
    {
        FREEMAPS_ERROR("in \"simulation_ensemble\", can not find  \"equation_ensembles\" \n");
        return false;
    }
    if( !db->is_string("equation_ensembles") )
    {
        FREEMAPS_ERROR("in \"simulation_ensemble\", \"equation_ensembles\" should be string, like this -->     equation_ensembles = \"ensemble_0\", \"ensemble_1\" \n");
        return false;
    }

    const std::vector<std::string>& equation_ensembles = db->get_string_vector ("equation_ensembles");
    for (std::size_t i = 0; i < equation_ensembles.size(); ++i)
    {
        const std::string& equation_ensemble_name = equation_ensembles[i];

        if( db->key_exists(equation_ensemble_name))
        {
            if( !db->is_database(equation_ensemble_name) )
            {
                FREEMAPS_ERROR("in \"simulation_ensemble\",  \"" + equation_ensemble_name + "\", should be database, like this--> A \n{\n ......\n} \n");
                return false;
            }

            const InputDatabase* db_ensemble = db->get_database(equation_ensemble_name);
            if( !db_ensemble->key_exists("type") )
            {
                FREEMAPS_ERROR("in \"simulation_ensemble\", in \"" + equation_ensemble_name + "\", can not find  \"type\" \n");
                return false;
            }
            if( !db_ensemble->is_string("type") )
            {
                FREEMAPS_ERROR("in \"simulation_ensemble\",  in \"" + equation_ensemble_name + "\", \"type\" should be string \n");
                return false;
            }

            const EquationEnsembleType eq_type = db_ensemble->get_enum<EquationEnsembleType>("type");

            if (Utilities::is_pde_equation_ensemble (eq_type))
            {
                PDEEquationEnsembleStrategy* equation_ensemble = PDEEquationEnsembleStrategy::create_object (
                            eq_type,
                            "simulation_ensemble",
                            equation_ensemble_name);

                bool check_ok = equation_ensemble->check_all_info_db(equation_ensemble_name, db_ensemble);
                if( !check_ok )
                {
                    return false;
                }
            }
            else
            {
                //TODO other type
            }
        }
        else
        {
            FREEMAPS_ERROR("in \"simulation_ensemble\", can not find database \"" + equation_ensemble_name + "\" , but in \"equation_ensembles\" exist\n");
            return false;
        }
    }

    return true;
}



void
SimulationEnsemble::get_from_input (
        const InputDatabase* db)
{
    this->prepare_for_use (db);

    db->insert_allowable_key_with_items ("equation_ensembles");

    db->check_allowable_keys ();

    this->add_key_for_checking_consistence (
        "equation_ensembles",
        "equation_ensemble's name should NOT be any level set's evolution mode name");

    /*
     * build equation_ensembles
     */
    if (db->key_exists ("equation_ensembles") && db->is_string("equation_ensembles"))
    {
        const std::vector<std::string>& equation_ensembles = db->get_string_vector ("equation_ensembles");
        for (std::size_t i = 0; i < equation_ensembles.size(); ++i)
        {
            const std::string& equation_ensemble_name = equation_ensembles[i];
            if (equation_ensemble_name == "level_set_ensemble")
            {
                LogMessage::use_improper_string_value (
                    db,
                    "equation_ensembles",
                    0,
                    "\"level_set_ensemble\" is reserved for level set ensemble and can NOT be used as a equation_ensemble name");
            }

            if (db->key_exists (equation_ensemble_name) && db->is_database(equation_ensemble_name))
            {
                db->check_requisite_key (equation_ensemble_name, DataType::DATABASE, false, false);
                const InputDatabase* eq_db = db->get_database (equation_ensemble_name);


                if ( eq_db->key_exists ("type") && eq_db->is_string("type"))
                {
                    eq_db->check_requisite_key ("type", DataType::STRING, false, false);
                    const EquationEnsembleType eq_type = eq_db->get_enum<EquationEnsembleType>("type");

                    if (Utilities::is_pde_equation_ensemble (eq_type))
                    {
                        PDEEquationEnsembleStrategy* equation_ensemble = PDEEquationEnsembleStrategy::create_object (
                                    eq_type,
                                    "simulation_ensemble",
                                    equation_ensemble_name);
                        equation_ensemble->get_from_input (eq_db);

                        d_pde_equation_ensembles.insert (std::make_pair (equation_ensemble_name, equation_ensemble));
                    }
                    else
                    {
#if 0
                        NonPDEEquationEnsembleStrategy* equation_ensemble = NonPDEEquationEnsembleStrategy::create_object (
                                    eq_type,
                                    "simulation_ensemble",
                                    equation_ensemble_name);
                        equation_ensemble->get_from_input (eq_db);

                        d_nonpde_equation_ensembles.insert (std::make_pair (equation_ensemble_name, equation_ensemble));
#endif
                    }
                }
            }
        }
    }
}



std::ostream&
operator << (
    std::ostream& os,
    const SimulationEnsemble& rhs)
{
    os << "simulation_ensemble" << RTN_FLAG << "{" << RTN_FLAG;

    std::set<std::string> eq_names = Utilities::get_map_keys (rhs.d_pde_equation_ensembles);
//    std::set<std::string> nonpde_eq_names = Utilities::get_map_keys (rhs.d_nonpde_equation_ensembles);
//    Utilities::insert (nonpde_eq_names, eq_names);

    if (!eq_names.empty())
    {
        os << LogMessage::get_prefix (ONE_TAB) << "equation_ensembles = ";
        os << LogMessage::get_log_info (
               Utilities::get_rearranged_set (eq_names, true, /*1*/3),
               static_cast<int>(std::string("equation_ensembles = ").size()) + ONE_TAB,
               true) << RTN_FLAG;

        std::ostringstream oss;
        for (std::map<std::string, PDEEquationEnsembleStrategy*>::const_iterator pos = rhs.d_pde_equation_ensembles.begin();
                pos != rhs.d_pde_equation_ensembles.end(); ++pos)
        {
            oss << RTN_FLAG << *(pos->second) << RTN_FLAG;
        }

//        for (std::map<std::string, NonPDEEquationEnsembleStrategy*>::const_iterator pos = rhs.d_nonpde_equation_ensembles.begin();
//                pos != rhs.d_nonpde_equation_ensembles.end(); ++pos)
//        {
//            oss << RTN_FLAG << *(pos->second) << RTN_FLAG;
//        }

        os << LogMessage::get_log_info (oss, ONE_TAB) << RTN_FLAG;
    }

    os << "}";

    return os;
}



void
SimulationEnsemble::add_pde_equation_ensembles(
        std::string equation_ensemble_name,
        PDEEquationEnsembleStrategy* pde_equation_ensemble)
{
    d_pde_equation_ensembles.insert(std::make_pair(equation_ensemble_name, pde_equation_ensemble));
}

} // namespace FreeMAPs
